Skip to content

security(git-id-switcher): SanitizedHtml branded type for buildDocumentHtml content#455

Merged
nullvariant merged 1 commit into
mainfrom
feat/issue-00237-sanitized-html-branded-type
Apr 9, 2026
Merged

security(git-id-switcher): SanitizedHtml branded type for buildDocumentHtml content#455
nullvariant merged 1 commit into
mainfrom
feat/issue-00237-sanitized-html-branded-type

Conversation

@nullvariant

Copy link
Copy Markdown
Owner

Summary

  • Promote buildDocumentHtml's content parameter from a plain string (contract-by-docstring) to a SanitizedHtml branded string type, enforcing the trust boundary between the markdown sanitizer and the webview HTML shell at compile time.
  • renderMarkdown becomes the single sanctioned origin of the brand (one as SanitizedHtml cast at its return site); all production call sites in documentationPublic.ts already flow through it.
  • getDocumentHtml wrapper signature upgraded and type re-exported from webview.ts so downstream consumers inherit the constraint.

Rationale

CSP (script-src 'nonce-…') blocks <script> execution but does not stop <img onerror> or javascript: URLs outside the link interceptor path. With the previous string type, a future caller could forget the sanitizer and the compiler would stay silent — the brand makes that class of regression a type error.

Notes

  • import type is used from documentationInternal.ts to htmlTemplates.ts to avoid promoting the existing one-way runtime edge (htmlTemplatesescapeHtmlEntities) into a real circular dependency. Rationale is commented inline on the import.
  • A documented, test-only asSanitizedHtml helper lets structural tests for buildDocumentHtml (shell/nav/escaping) bypass the sanitizer; the sanitizer itself has its own dedicated coverage in documentation.test.ts.
  • Security review delegated to a security-engineer persona: no Critical/Medium findings. The single Low finding (circular-import fragility) was folded in as an inline comment.

Test plan

  • npx tsc --noEmit passes
  • npm run lint passes with --max-warnings=0
  • npm run test:coverage passes; htmlTemplates.ts and documentationInternal.ts retain 100% statement coverage
  • Verified no other as SanitizedHtml casts exist in production code

…uildDocumentHtml content

Promote the `content` parameter of `buildDocumentHtml` from a plain
`string` (contract-by-docstring) to a `SanitizedHtml` branded string type
so that the trust boundary between the markdown sanitizer and the webview
HTML shell is enforced at compile time. Previously, a future caller could
pass an unsanitized `string` and the only remaining defence was CSP —
which does not block `<img onerror>` or `javascript:` URLs outside the
link interceptor path.
Changes:
- htmlTemplates.ts: export `SanitizedHtml = string & { readonly __brand }`
  and retype `buildDocumentHtml(content: SanitizedHtml)`.
- documentationInternal.ts: `renderMarkdown` now returns `SanitizedHtml`,
  with a single sanctioned `as SanitizedHtml` cast at its return site
  (the sole origin of the brand in production code). Type-only import
  from htmlTemplates to avoid promoting the existing one-way runtime
  import edge into a circular dependency.
- webview.ts: `getDocumentHtml` wrapper signature upgraded and type
  re-exported so downstream consumers inherit the constraint.
- htmlTemplates.test.ts: isolated, documented `asSanitizedHtml` test
  helper for structural tests that exercise the shell rather than the
  sanitizer (renderMarkdown has its own dedicated coverage in
  documentation.test.ts).
Security review (delegated, security-engineer persona): no Critical or
Medium findings. Single Low finding (circular-import fragility) folded
in as an inline comment on the type-only import.
tsc, eslint (--max-warnings=0), and test:coverage all pass;
htmlTemplates.ts and documentationInternal.ts retain 100% statement
coverage.
Signed-off-by: Null;Variant <null@nullvariant.com>

🖥️ IDE: [VS Code](https://code.visualstudio.com/)
🔌 Extension: [Claude Code](https://claude.ai/download)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Model-Raw: claude-opus-4-6
@nullvariant-mimi

Copy link
Copy Markdown
Contributor

🐰 Mimi's Validation Report ✅

All checks are looking good! Great job! 🎉

⏳ Some checks are still running. I will keep watching!


バリデーターを通してくださいね

This report was carefully prepared by nullvariant-mimi[bot]

@nullvariant-slow

Copy link
Copy Markdown
Contributor

🦥 Slow's Code Review 😩

...yawn... Do I really have to review this?

⚠️ TOO LONG... I can barely keep my eyes open reading these:

File Lines

| extensions/git-id-switcher/src/test/htmlTemplates.test.ts | 1003 |
| extensions/git-id-switcher/src/ui/documentationInternal.ts | 493 |
| extensions/git-id-switcher/src/ui/htmlTemplates.ts | 551 |

Split it up... reading long files is exhausting.


働きたくないでござる

This review was reluctantly filed by nullvariant-slow[bot]

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA c59ad1d.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@nullvariant-ciel

nullvariant-ciel Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🕊️ Ciel's Mediation 🌤️

*~~ floating down from the clouds ~~ The zoo seems a bit noisy today...*

2 zoo members have reviewed this PR.

Zoo Member Status
🦥 Slow Commented
🐰 Mimi Commented

⚖️ The zoo has mixed opinions. Some are concerned, some are fine with it. Please review each comment carefully and make the final call.


まあまあ、ほどほどに。

This mediation was peacefully delivered by nullvariant-ciel[bot]

@sonarqubecloud

sonarqubecloud Bot commented Apr 9, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@nullvariant-justice nullvariant-justice Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚖️ Justice grants passage. CI checks passed — this code meets the garden's standards.

@nullvariant nullvariant merged commit 0fee85f into main Apr 9, 2026
35 of 36 checks passed
@nullvariant nullvariant deleted the feat/issue-00237-sanitized-html-branded-type branch April 9, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant